#include <drivers/uart.h>
#include <kernel/initcall.h>
#include <suniv.h>
#include <drivers/irq.h>
#include <drivers/gpio.h>
#include <drivers/clock.h>
#include <kernel/syslog.h>
#include <drivers/platform.h>
#include <kernel/fdt.h>
#include <io.h>


#define    UART_RBR_OFFSET                0x00
#define    UART_THR_OFFSET                0x00
#define    UART_DLL_OFFSET                0x00
#define    UART_IER_OFFSET                0x04
#define    UART_DLH_OFFSET                0x04
#define    UART_IIR_OFFSET                0x08
#define    UART_FCR_OFFSET                0x08
#define    UART_LCR_OFFSET                0x0C
#define    UART_MCR_OFFSET                0x10
#define    UART_LSR_OFFSET                0x14
#define    UART_MSR_OFFSET                0x18
#define    UART_SCR_OFFSET                0x1C
#define    UART_USR_OFFSET                0x7C
#define    UART_TFL_OFFSET                0x80
#define    UART_RFL_OFFSET                0x84
#define    UART_HSK_OFFSET                0x88
#define    UART_HALT_OFFSET               0xA4
#define    UART_DBG_DLL_OFFSET            0xB0
#define    UART_DBG_DLH_OFFSET            0xB4


#define    UART_PARITY_NONE               0
#define    UART_PARITY_ODD                1
#define    UART_PARITY_EVEN               3
#define    UART_LEN_5B                    0
#define    UART_LEN_6B                    1
#define    UART_LEN_7B                    2
#define    UART_LEN_8B                    3
#define    UART_IEN_RBF                   0
#define    UART_IEN_TBE                   1
#define    UART_IEN_LINE                  2
#define    UART_IEN_MODEM                 3
#define    UART_IEN_THRE                  7
#define    UART_IID_MODEM                 0x00
#define    UART_IID_NONE                  0x01
#define    UART_IID_TX_E                  0x02
#define    UART_IID_RX_NE                 0x04
#define    UART_IID_RX_ER                 0x06
#define    UART_IID_BUSY                  0x07
#define    UART_IID_TIMEOUT               0x0C
#define    UART_LSR_DR                    (1 << 0)
#define    UART_LSR_OE                    (1 << 1)
#define    UART_LSR_PE                    (1 << 2)
#define    UART_LSR_FE                    (1 << 3)
#define    UART_LSR_BI                    (1 << 4)
#define    UART_LSR_THRE                  (1 << 5)
#define    UART_LSR_TEMT                  (1 << 6)
#define    UART_LSR_FIFOERR               (1 << 7)


static void uart_set_baudrate(uint32_t io_base, uint32_t clock, uint32_t baudrate)
{
    uint16_t val = (uint16_t)(clock / baudrate / 16UL);

    write32(io_base + UART_LCR_OFFSET, (read32(io_base + UART_LCR_OFFSET) | (1 << 7)));
    write32(io_base + UART_DLL_OFFSET, val & 0xFF);
    write32(io_base + UART_DLH_OFFSET, (val >> 8) & 0xFF);
    write32(io_base + UART_LCR_OFFSET, (read32(io_base + UART_LCR_OFFSET) & ~(1 << 7)));
}


static inline void uart_enable_interrupt(uint32_t uart, int int_n)
{
    write32(uart + UART_IER_OFFSET, (read32(uart + UART_IER_OFFSET) | (1 << int_n)));
}

static inline void uart_disable_interrupt(uint32_t uart, int int_n)
{
    write32(uart + UART_IER_OFFSET, (read32(uart + UART_IER_OFFSET) & ~(1 << int_n)));
}


static void suniv_uart_init(struct uart_master *master, uint32_t baudrate)
{
    uint32_t uart = master->io_base;

    write32(uart + UART_IER_OFFSET, 0x00); // Disable interrupts
    write32(uart + UART_FCR_OFFSET, 0x07); // Enable and reset FIFO
    write32(uart + UART_MCR_OFFSET, 0x00); // Disable flow control

    uart_set_baudrate(uart, master->clock_freq_hz, baudrate);

    uart_enable_interrupt(uart, UART_IEN_RBF); // Enable receive buffer full interrupt

    uint32_t val = read32(uart + UART_LCR_OFFSET) & ~0x3F;
    val |= (UART_LEN_8B << 0) | (0 << 2) | (UART_PARITY_NONE << 3); // 8bit, parity off, 1 stop
    write32(uart + UART_LCR_OFFSET, val);
}


static void suniv_uart_start(struct uart_device *pdev)
{

}


static void suniv_uart_stop(struct uart_device *pdev)
{

}


static void suniv_uart_put_char(struct uart_device *dev, char ch)
{
    uint32_t uart = dev->master->io_base;

	while((read32(uart + UART_USR_OFFSET) & (0x1 << 1)) == 0);
	write32(uart + UART_THR_OFFSET, ch);
}


static void suniv_uart_put_string(struct uart_device *dev, char *str)
{
    uint32_t uart = dev->master->io_base;

    while(*str) {
        while((read32(uart + UART_USR_OFFSET) & (0x1 << 1)) == 0);
        write32(uart + UART_THR_OFFSET, *str);
        str++;
    }
}


static char suniv_uart_get_char(struct uart_device *dev)
{
    uint32_t uart = dev->master->io_base;
    return (uint8_t)read32(uart + UART_RBR_OFFSET);
}


static void suniv_uart_set_baudrate(struct uart_device *dev, uint32_t baudrate)
{
    uint32_t uart = dev->master->io_base;
    uint32_t clock = dev->master->clock_freq_hz;
    
    uart_set_baudrate(uart, clock, baudrate);
}


static struct uart_ops suniv_uart_ops = {
    .start = suniv_uart_start,
    .stop = suniv_uart_stop,
    .put_char = suniv_uart_put_char,
    .put_string = suniv_uart_put_string,
    .get_char = suniv_uart_get_char,
    .set_baudrate = suniv_uart_set_baudrate,
};


extern void spl_uart_puts(const char *s);


static int suniv_uart_probe(struct platform_device *dev)
{
    fdt_node_t *uart_node = dev->device.dt_node;

    const char *uart_clock = fdt_read_prop_string(uart_node, "clock-bus");
    if(uart_clock == NULL) {
        return -1;
    }

    size_t clock_bit = -1;
    if(fdt_read_prop_int(uart_node, "clock-bit", &clock_bit)) {
        PLATFORM_SYSLOG_ERROR("uart: no clock-bit property");
        return -1;
    }

    clock_enable_by_name(uart_clock, clock_bit);

    size_t irq_num = 0;
    if(fdt_read_prop_int(uart_node, "interrupts", &irq_num)) {
        PLATFORM_SYSLOG_ERROR("uart: no interrupts property");
    }

    size_t baudrate = 0;
    if(fdt_read_prop_int(uart_node, "baudrate", &baudrate)) {
        PLATFORM_SYSLOG_ERROR("uart: no baudrate property");
        return -1;
    }

    struct uart_gpio pins = {-1, -1};
    if(uart_master_get_gpio_from_fdt(uart_node, &pins)) {
        return -1;
    }

    struct uart_master *master = kmalloc(sizeof(struct uart_master));
    if(master == NULL) {
        PLATFORM_SYSLOG_ERROR("uart: kmalloc failed");
        return -1;
    }

    master->ops = &suniv_uart_ops;
    master->pins = pins;
    master->io_base = dev->io_base;
    master->clock_freq_hz = clock_get_freq_by_name(uart_clock);

    platform_set_drvdata(dev, master);

    suniv_uart_init(master, baudrate);

    fdt_node_t *child =  NULL;
    fdt_for_each_node_child(uart_node, child) {
        const char *comp = fdt_read_prop_string(child, "compatible");
        if(comp == NULL) {
            PLATFORM_SYSLOG_ERROR("uart: no compatible property");
            continue;
        }

        const char *status = fdt_read_prop_string(child, "status");
        if(status != NULL && strcmp(status, "disabled") == 0) {
            PLATFORM_SYSLOG_INFO("uart: status is disabled");
            continue;
        }

        struct uart_device *udev = kmalloc(sizeof(struct uart_device));
        if(udev == NULL) {
            PLATFORM_SYSLOG_ERROR("uart: kmalloc failed");
            return -1;
        }

        udev->device.name = uart_node->name;
        udev->device.compatible = comp;
        udev->device.driver = NULL;
        udev->master = master;

        if(uart_device_register(udev) < 0) {
            PLATFORM_SYSLOG_ERROR("uart: uart_device_register failed");
            kfree(udev);
            return -1;
        }

        if(irq_num) {
            udev->irq_num = irq_num;
        }
    }

    return 0;
}


static void suniv_uart_remove(struct platform_device *dev)
{
    
}


static struct of_device_id suniv_uart_match[] = {
    {.compatible = "suniv-uart", },
    { }
};


static struct platform_driver suniv_uart_driver = {
    .driver = {
        .name = "suniv-uart",
        .of_match_table = suniv_uart_match,
    },
    .probe = suniv_uart_probe,
    .remove = suniv_uart_remove,
};


static int suniv_uart_driver_register(void)
{
    return platform_driver_register(&suniv_uart_driver);
}


core_initcall(suniv_uart_driver_register);
